#ifndef __block_queue_hpp_lkjjwfejiojoiwef93323290i98u9hy7yf43jifejji2efjnvnjv__
#define __block_queue_hpp_lkjjwfejiojoiwef93323290i98u9hy7yf43jifejji2efjnvnjv__

#include <list>
#include <mutex>
#include <functional>
#include <condition_variable>

#include "optional.hpp"

namespace hude
{
	namespace utils
	{
		/**********************************************************************************************
		 * @brief block_queue_t
		 */
		template< typename _value >
		class block_queue_t
		{
		public:
			typedef _value value_t;
			typedef std::list< value_t > queue_t;
			typedef typename queue_t::iterator referen_t;
			typedef typename std::unique_lock< std::mutex > autolock_t;
			typedef std::function< void( value_t& ) > on_handle_t;

			class operator_t
			{
			private:
				block_queue_t *queue_;

			public:
				operator_t( block_queue_t* queue ) : queue_( queue ) {
					queue_->mutex_.lock(); }

				~operator_t() { queue_->mutex_.unlock(); }

				queue_t* operator->() { return &queue_->queue_; }
				const queue_t* operator->() const { return &queue_->queue_; }

				queue_t& operator*() { return queue_->queue_; }
				const queue_t& operator*() const { return queue_->queue_; }
			};

			class producter_t
			{
			private:
				block_queue_t *queue_;
				bool empty_;

			public:
				producter_t( block_queue_t* queue ) : queue_( queue ) {
					queue_->mutex_.lock(); empty_ = queue_->queue_.empty(); }

				~producter_t() { queue_->mutex_.unlock();
					if( empty_ && !queue_->queue_.empty() ) queue_->condition_.notify_all(); }

				queue_t* operator->() { return &queue_->queue_; }
				const queue_t* operator->() const { return &queue_->queue_; }

				queue_t& operator*() { return queue_->queue_; }
				const queue_t& operator*() const { return queue_->queue_; }
			};

			class consumer_t
			{
			private:
				block_queue_t *queue_;

			public:
				consumer_t( block_queue_t* queue, std::chrono::milliseconds ms = std::chrono::milliseconds() ) : queue_( queue ) {
					queue_->wait( ms ); queue_->mutex_.lock(); }

				~consumer_t() { queue_->mutex_.unlock(); }

				queue_t* operator->() { return &queue_->queue_; }
				const queue_t* operator->() const { return &queue_->queue_; }

				queue_t& operator*() { return queue_->queue_; }
				const queue_t& operator*() const { return queue_->queue_; }
			};

			class freedom_t
			{
			private:
				block_queue_t *queue_;

			public:
				freedom_t( block_queue_t* queue, std::chrono::milliseconds ms = std::chrono::milliseconds() ) : queue_( queue )
				{
					queue_->wait( ms );
				}

				~freedom_t() {}

				queue_t* operator->() { return &queue_->queue_; }
				const queue_t* operator->() const { return &queue_->queue_; }

				queue_t& operator*() { return queue_->queue_; }
				const queue_t& operator*() const { return queue_->queue_; }
			};

			friend producter_t;
			friend consumer_t;
			friend freedom_t;

		public:
			block_queue_t();
			~block_queue_t();

			void push( value_t&& value, bool front = false );
			void push( const value_t& value, bool front = false );

			void pop( bool front = true );

			value_t& front();
			const value_t& front() const;

			value_t& back();
			const value_t& back() const;

			size_t size() const;
			bool empty() const;
	
			void exit_wait();

			bool wait( std::chrono::milliseconds ms = std::chrono::milliseconds() );

			bool consume( on_handle_t, bool front = true, hude::optional_t< std::chrono::milliseconds > = std::chrono::milliseconds{} );

			std::mutex& mutex();

		private:
			bool _wait( autolock_t&, hude::optional_t< std::chrono::milliseconds > = std::chrono::milliseconds{} );

		private:
			mutable std::mutex mutex_;
			mutable std::condition_variable condition_;
			queue_t queue_;

		private:
			block_queue_t( const block_queue_t& );
			block_queue_t& operator=( const block_queue_t& );

		};
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		block_queue_t< _value >::block_queue_t()
		{
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		block_queue_t< _value >::~block_queue_t()
		{
			exit_wait();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		void block_queue_t< _value >::push( value_t&& value, bool front )
		{
			autolock_t al( mutex_ );

			if( front )
			{
				queue_.emplace_front( std::forward< value_t >( value ) );
			} else {
				queue_.emplace_back( std::forward< value_t >( value ) );
			}

			condition_.notify_one();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		void block_queue_t< _value >::push( const value_t& value, bool front )
		{
			autolock_t al( mutex_ );

			if( front )
			{
				queue_.push_front( value );
			} else {
				queue_.push_back( value );
			}

			condition_.notify_one();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		void block_queue_t< _value >::pop( bool front )
		{
			autolock_t al( mutex_ );

			if( queue_.empty() )
				return;

			if( front )
			{
				queue_.pop_front();
			} else {
				queue_.pop_back();
			}
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		bool block_queue_t< _value >::consume( on_handle_t handler, bool front, hude::optional_t< std::chrono::milliseconds > ms )
		{
			value_t value;

			{
				autolock_t al( mutex_ );

				if( ! _wait( al, ms ) ) return false;

				if( front )
				{
					value = queue_.front();
					queue_.pop_front();
				} else {
					value = queue_.back();
					queue_.pop_back();
				}
			}

			handler( value );

			return true;
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		typename block_queue_t< _value >::value_t& block_queue_t< _value >::front()
		{
			autolock_t al( mutex_ );
	
			_wait( al );
	
			return queue_.front();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		const typename block_queue_t< _value >::value_t& block_queue_t< _value >::front() const
		{
			autolock_t al( mutex_ );
	
			_wait( al );
	
			return queue_.front();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		typename block_queue_t< _value >::value_t& block_queue_t< _value >::back()
		{
			autolock_t al( mutex_ );
	
			_wait( al );
	
			return queue_.back();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		const typename block_queue_t< _value >::value_t& block_queue_t< _value >::back() const
		{
			autolock_t al( mutex_ );
	
			_wait( al );
	
			return queue_.back();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		size_t block_queue_t< _value >::size() const
		{
			autolock_t al( mutex_ );
			return queue_.size();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		bool block_queue_t< _value >::empty() const
		{
			autolock_t al( mutex_ );
			return queue_.empty();
		}
	
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		void block_queue_t< _value >::exit_wait()
		{
			autolock_t al( mutex_ );

			//std::notify_all_at_thread_exit( condition_, std::move( al ) );
			condition_.notify_all();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		bool block_queue_t< _value >::wait( std::chrono::milliseconds ms )
		{
			autolock_t al( mutex_ );

			return _wait( al, ms );
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		bool block_queue_t< _value >::_wait( autolock_t& al, hude::optional_t< std::chrono::milliseconds > ms )
		{
			if( queue_.empty() )
			{
				if( ! ms )
				{
					return false;
				} else
				if( *ms == std::chrono::milliseconds() )
				{
					condition_.wait( al );
				} else {
					if( condition_.wait_for( al, *ms ) == std::cv_status::timeout )
						return false;
				}
			}

			return ! queue_.empty();
		}
		
		//----------------------------------------------------------------------------------------------
		template< typename _value >
		std::mutex& block_queue_t< _value >::mutex()
		{
			return mutex_;
		}
		
	} //namespace utils

	template< typename _value >
	using blok_queue_t = utils::block_queue_t< _value >;
} //namespace hude

#endif //__block_queue_hpp_lkjjwfejiojoiwef93323290i98u9hy7yf43jifejji2efjnvnjv__
